/*
 *  This file builds test server for this project
 *  server only accepts input from redis, format is json.
 *  web front end ---> go server
 *  go server     ---> release clock on redis --> wait for task end --> return to server
 *  c++ server    ---> wait for redis queue --> handle dip  --> release finish queue
 *
 *  Dependence:
 *  redox: https://github.com/hmartiro/redox
 *  sudo apt-get install git cmake build-essential
 *  sudo apt-get install libhiredis-dev libev-dev libb64-dev
 *
 * Note:
 *  the follow json format are stored in redis, the image server only interact with
 *  redis, no files in filesystem or other part are related in
 *
 *  json file format
 *  {
 *      mata_data {
 *          id                      : int  // id in mysql database
 *          image_type              : string, can be ha,ma,ve,ex or other namep
 *          // information of patient
 *      }
 *      files {
 *          original_image          : string/base64
 *          out_image               : string/base64
 *      }
 *      quantifier_result{
 *          area                 : int
 *          percentage           : float
 *          count                : int
 *      }
 *  }
 *
 *          *** Redis kes ***
 *  task_queue      : list of tasks, lpush by web server, brpop by image server
 *  finish_queue    : list of finished tasks, lpush by image server, brpop by web server
 *
 */

#include <iostream>
//#define GOOGLE_STRIP_LOG 1

#include <redox.hpp>
#include <sstream>
#include <b64/encode.h>
#include <b64/decode.h>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <backward.hpp>
#include "json.hpp"
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include <opencv2/opencv.hpp>
#include <fstream>
#include "exudate_extract.h"
#include "vessel_extract.h"
#include "tools.h"

using redox::Redox;
using google::INFO;
using google::WARNING;
using google::FATAL;
using google::ERROR;
using std::string;
using std::ofstream;
using json = nlohmann::json;
using std::stringstream;
using cv::Mat;
using cv::imread;
using std::vector;


Mat decodeImage(string ba64file);

Mat getMask(Mat _img, double thresh = 0.0001);

Mat imageProcess(Mat inImage, string type);

string encodeImage(Mat img);

int calNumber(CommonImage &cimg, int area = 0, int upBound = -1);

int calArea(CommonImage &cimg);

string calLocation(CommonImage &img);

string calStrenth(CommonImage &cimg, int thresh1 = 20, int thresh2 = 50);

string calType(string t);

int main(int argc, char **argv) {

    google::SetCommandLineOption("alsologtostderr", "true");
    google::SetCommandLineOption("colorlogtostderr", "true");
    google::InitGoogleLogging(argv[0]);
    google::SetUsageMessage(
            "Digital Image Processing server\n"
    );
    google::ParseCommandLineFlags(&argc, &argv, true);
    backward::SignalHandling sh;
    if (!sh.loaded()) {
        LOG(ERROR) << "BACKWARD NOT LOADED";
    }

    LOG(INFO) << "server started";

    string redis_server_host = "127.0.0.1";
    int redis_server_port = 6379;

    Redox rdx;
    if (!rdx.connect(redis_server_host, redis_server_port)) return 1;
    while (1) {
        auto &c = rdx.commandSync<std::vector<std::string>>({"brpop", "task_queue", "0"});
        if (c.ok()) {
            if (c.reply()[0] == "task_queue") {
                // start decode
                json j = json::parse(c.reply()[1]);
                LOG(INFO) << "Processing file " << j["mata_data"]["task_id"]
                          << " Method " << j["mata_data"]["image_type"];
                Mat decodedImg = decodeImage(j["files"]["original_image"]);
                CHECK(!decodedImg.empty()) << "image decode failed";
                // TODO add code to finish the communication
                Mat oimage = imageProcess(decodedImg, j["mata_data"]["image_type"]);
                string encodedStr = encodeImage(oimage);
                j["files"]["output_image"] = encodedStr;
                CommonImage ocimg(oimage);
                int area = calArea(ocimg);
                j["quantifier_result"]["area"] = area;
                j["quantifier_result"]["percentage"] = double(area) / double(oimage.rows) / double(oimage.cols);
                j["quantifier_result"]["count"] = calNumber(ocimg);
                j["quantifier_result"]["desp"] =
                        calLocation(ocimg) + calStrenth(ocimg) + calType(j["mata_data"]["image_type"]);

                LOG(INFO) << area << " " << j["quantifier_result"]["percentage"] << " "
                          << j["quantifier_result"]["count"];
                string finishPayload = j.dump();
                auto &finisheContex = rdx.commandSync<std::vector<std::string>>(
                        {"RPUSH", "finish_queue", finishPayload}
                );
                if (!finisheContex.ok()) {
                    LOG(ERROR) << finisheContex.lastError();
                } else {
                    LOG(INFO) << "finished task " << j["mata_data"]["task_id"];
                }
            }
        }
//        break;
    }

    rdx.disconnect();

//    base64::encoder E;
//    E.encode(std::cin, std::cout);
    return 0;

}

Mat decodeImage(string ba64file) {
    base64::decoder decoder;
    stringstream infile, outfile;
    infile << ba64file;
    decoder.decode(infile, outfile);
    string fileContent = outfile.str();
    LOG(INFO) << "size of the file :" << fileContent.size();
    vector<char> buffer(fileContent.begin(), fileContent.end());
    Mat decodedImg = cv::imdecode(buffer, cv::IMREAD_COLOR);
    return decodedImg;
}

/** @brief encode a image to base64 string, using jpeg standard
The funciton encode a Mat image into jpeg binary, and then encode the jpeg data into base64 format

@param img Input array or image.
*/
string encodeImage(Mat img) {
    vector<uchar> buf;
    cv::imencode(".jpg", img, buf);
    stringstream infile, outfile;
    infile.write((const char *) buf.data(), buf.size());
    base64::encoder encoder;
    encoder.encode(infile, outfile);
    return outfile.str();
}

Mat getMask(Mat _img, double thresh) {
    Mat img;
    _img.convertTo(img, CV_32FC(_img.channels()), 1.0 / 255.0);
    vector<Mat> image_channels;
    split(img, image_channels);
    Mat rChannel(img.rows, img.cols, CV_32FC1, cv::Scalar(0));
    for (int i = 1; i < 2; i++) {
        rChannel += image_channels[i].mul(image_channels[i]);
    }
    double maxValue, minValue;
    minMaxLoc(rChannel, &minValue, &maxValue);
    return rChannel > thresh * maxValue;
}

/** @brif This function is the main function that implements the whole image
 * processing procedure.
 *
 * @param inImage Imput image, which should be a cv_8uC3 type
 * @param type   type of processing,
 *
 */
Mat imageProcess(Mat inImage, string type) {
//    Mat mask = getMask(inImage);
    Mat outImage;
    for (;;) {
        int i = rand();
        DRImage drImage(inImage, i);
        drImage.write(i);
        if (type == "ex") {
            CommonImage Hardleakage(leakage_preprocess(drImage.getOriginal()), "hard");
            Hardleakage.write(i);
            outImage = Hardleakage.getOriginal();
            break;
        }
        if (type == "ve") {
            CommonImage vesselImg(vessel_seg(drImage.getOriginal()), "vesselImg");
            vesselImg.write(i);
            outImage = vesselImg.getOriginal();
            break;
        }
        if (type == "ha") {
            CommonImage bleedImg(bleed_process(drImage.getOriginal()), "bleed");
            bleedImg.write(i);
            outImage = bleedImg.getOriginal();
            break;
        }
        if (type == "ma") {
            CommonImage bleedImg(bleed_process(drImage.getOriginal()), "bleed");
            bleedImg.write(i);
            outImage = bleedImg.getOriginal();
            break;
        }
        LOG(ERROR) << "Method not impleted";
        outImage = inImage;
        break;
    }
    return outImage;
}

int calNumber(CommonImage &cimg, int area, int upBound) {
    Mat img = cimg.getByte().getOriginal();
    Mat label, stats, centroids;
    if (upBound <= 0) {
        upBound = cimg.getOriginal().rows * cimg.getOriginal().cols;
    }
    connectedComponentsWithStats(img, label, stats, centroids, 4, CV_32S);
    int num = 0;
    for (int i = 1; i < stats.size[0]; i++) {
        if (stats.at<int32_t>(i, cv::CC_STAT_AREA) > area && stats.at<int32_t>(i, cv::CC_STAT_AREA) < upBound)
            num++;
    }
    return num;
}

int calArea(CommonImage &cimg) {
    Mat img = cimg.getByte().getOriginal();
    return (int) sum(img / 255).val[0];
}

string calLocation(CommonImage &img) {
    Mat imgMat = img.getByte().getOriginal();
    CHECK_EQ(imgMat.depth(), CV_8U);
    CHECK_EQ(imgMat.channels(), 1);
    double r = 0, c = 0;
    double counter = 0;
    for (int i = 0; i < imgMat.rows; i++) {
        for (int j = 0; j < imgMat.cols; j++) {
            if (imgMat.at<uchar>(i, j)) {
                r += i;
                c += j;
                counter++;
            }
        }
    }
    r /= counter;
    c /= counter;
    r /= imgMat.rows;
    c /= imgMat.cols;
    string retval = "";
    if (c < 0.375) {
        retval += "视乳头";
    } else if (c < 0.625) {
        retval += "黄斑区";
    } else {
        retval += "后极部";
        return retval;
    }
    if (r > 0.5) {
        retval += "下方";
    } else {
        retval += "上方";
    }
    return retval;
}

string calStrenth(CommonImage &cimg, int thresh1, int thresh2) {
    Mat img = cimg.getByte().getOriginal();
    Mat label, stats, centroids;
    connectedComponentsWithStats(img, label, stats, centroids, 4, CV_32S);
    int area = 0;
    for (int i = 1; i < stats.size[0]; i++) {
        area += stats.at<int32_t>(i, cv::CC_STAT_AREA);
    }
    area /= stats.size[0];
    if (area < 5) {
        return "未见";
    } else if (area < thresh1) {
        return "疑似";
    } else if (area < thresh2) {
        return "散在";
    } else {
        return "出现";
    }
}

string calType(string t) {
    if (t == "ve") return "";
    if (t == "ma") return "微血管瘤";
    if (t == "ha") return "出血";
    if (t == "ex") return "渗出";
    return "";
}
